﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.ZLog;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using System.Collections.ObjectModel;

namespace UtilZ.Dotnet.Ex.Communication.Net.Telnet
{
    /// <summary>
    /// 
    /// </summary>
    public class TelnetServer : IDisposable
    {
        private readonly Socket _listenSocket;
        private readonly ThreadEx _listenThread;

        private readonly object _lock = new object();
        private bool _listening = false;
        private readonly List<ListenTelnetClient> _telnetClientList = new List<ListenTelnetClient>();

        /// <summary>
        /// 监听到的连接改变事件
        /// </summary>
        public event EventHandler<NetConnectedChangedArgs> ConnectChanged;

        private void OnRaiseConnectChanged(ConnectionStatus status, DisconnectMode disconnectMode, EndPoint remoteEndPoint)
        {
            this.ConnectChanged?.Invoke(this, new NetConnectedChangedArgs(status, disconnectMode, this._listenSocket.LocalEndPoint, remoteEndPoint));
        }

        private readonly Func<TelnetMessageArgs, string> _processMessage;

        private Encoding _messageEncoding = null;
        /// <summary>
        /// 消息编码
        /// </summary>
        public Encoding MessageEncoding
        {
            get { return _messageEncoding; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value), "消息编码不能为null");
                }
                _messageEncoding = value;
            }
        }


        private string _connectedPromptMessage = null;
        /// <summary>
        /// 获取或设置连接提示信息
        /// </summary>
        public string ConnectedPromptMessage
        {
            get { return this._connectedPromptMessage; }
            set { this._connectedPromptMessage = value; }
        }


#if NET4_0
        private readonly Dictionary<string, TelnetUserInfo> _userInfoDic = null;
#else
        private readonly IReadOnlyDictionary<string, TelnetUserInfo> _userInfoDic = null;
#endif 

        /// <summary>
        /// Initializes a new instance of the <see cref="TelnetServer"/> class.
        /// </summary>
        /// <param name="processMessage">处理消息回调</param>
        /// <param name="config">telnet配置</param>
        public TelnetServer(Func<TelnetMessageArgs, string> processMessage, TelnetConfig config)
        {
            this._processMessage = processMessage;

            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            var listenEndPoint = new IPEndPoint(config.IPAddress, config.Port);
            this._listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this._listenSocket.Bind(listenEndPoint);
            this._listenSocket.Listen(config.Backlog);


            if (config.Users != null)
            {
                var userInfoDic = new Dictionary<string, TelnetUserInfo>();
                foreach (var user in config.Users)
                {
                    userInfoDic.Add(user.UserName, user);
                }
                this._userInfoDic = userInfoDic;
            }


            this._listenThread = new ThreadEx(this.ListenThreadMethod, "Telnet连接监听线程", true);
        }


        private void ListenThreadMethod(ThreadPara threadPara)
        {
            try
            {
                Socket clientSocket;
                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        clientSocket = this._listenSocket.Accept();
                    }
                    catch (SocketException)
                    {
                        break;
                    }

                    this.OnRaiseConnectChanged(ConnectionStatus.Opened, DisconnectMode.Active, clientSocket.RemoteEndPoint);

                    lock (this._lock)
                    {
                        ListenTelnetClient telnetClient;
                        if (this._userInfoDic == null)
                        {
                            telnetClient = new ListenTelnetClient(clientSocket, this.TelnetClientDisconnect, this._messageEncoding, this._processMessage, this._connectedPromptMessage);
                        }
                        else
                        {
                            telnetClient = new ListenTelnetClient(clientSocket, this.TelnetClientDisconnect, this.CheckAccount, this._messageEncoding, this._processMessage, this._connectedPromptMessage);
                        }

                        this._telnetClientList.Add(telnetClient);
                        ZLoger.Info($"\"{NetHelper.GetIPString(clientSocket.RemoteEndPoint)}\"telnet建立");
                    }
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private TelnetUserInfo CheckAccount(string userName)
        {
            TelnetUserInfo userInfo;
            this._userInfoDic.TryGetValue(userName, out userInfo);
            return userInfo;
        }

        /// <summary>
        /// 启动监听
        /// </summary>
        public void Start()
        {
            lock (this._lock)
            {
                if (this._listening)
                {
                    return;
                }

                this._listenThread.Start();
                this._listening = true;
            }
        }

        private void TelnetClientDisconnect(ListenTelnetClient telnetClient, DisconnectMode disconnectMode)
        {
            try
            {
                EndPoint remoteEndPoint = null;
                try
                {
                    remoteEndPoint = telnetClient.Socket.RemoteEndPoint;
                }
                catch (ObjectDisposedException)
                {

                }

                this.OnRaiseConnectChanged(ConnectionStatus.Closed, disconnectMode, remoteEndPoint);
                this._telnetClientList.Remove(telnetClient);
                telnetClient.Dispose();
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        /// <summary>
        /// 停止监听
        /// </summary>
        public void Stop()
        {
            lock (this._lock)
            {
                if (this._listening)
                {
                    this.PrimitiveStop();
                }
            }
        }

        private void PrimitiveStop()
        {
            this._listenThread.Stop();
            this._listenSocket.Close();

            foreach (var client in this._telnetClientList)
            {
                client.Dispose();
            }
            this._telnetClientList.Clear();
            this._listening = false;
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            lock (this._lock)
            {
                if (this._listening)
                {
                    this.PrimitiveStop();

                    this._listenThread.Dispose();
                    this._listenSocket.Dispose();
                }
            }
        }
    }
}
